package game.engine;
import java.io.Serializable;
import java.util.Vector;

import main.Config;

import org.lwjgl.opengl.GL11;
import game.Game;
import game.engine.GSync.SyncType;
import game.objects.GOPlayer;
import game.objects.GameObject;
import game.waypoint.Path;
import game.waypoint.WaypointSystem;
import graphics.main.Graphics;
import graphics.objects.GraphicsObject;
import graphics.system.Color;
import graphics.system.GLTools;
import physics.Body;
import physics.World;
import physics.joints.JAngleJoint;
import physics.joints.JSpring;
import physics.joints.Joint;
import physics.joints.TwoBodyJoint;
import physics.shapes.Circle;
import physics.shapes.Polygon;
import physics.shapes.Shape;
import tools.DebugDraw;
import tools.Floatmath;
import tools.Help;
import tools.Point;
import tools.Profiler;
/**
 * This class manages the entire game engine including graphics, physics and later networking. It will handle each part separately and afterwards synchronize physics <-> graphics physics <-> network graphics <-> network
 * 
 * @author toni
 * 
 */
public class Engine implements Serializable {
	public Game game;
	public World world;
	public Graphics graphics;
	private boolean dophysics;
	private boolean drawNormals;
	private WaypointSystem wsys;
	public Path debugpath;
	public Engine(Game game) {
		this.game = game;
		world = new World();
		graphics = new Graphics();
		dophysics = true;
		drawNormals = false;
	}
	public void handle() {
		Profiler.startPhysics();
		if (dophysics) {
			for (int i = 0; i < Config.PHYSICSSPEED; i++) {
				world.handle();
			}
		}
		Profiler.stopPhysics();
		Profiler.startGraphics();
		graphics.handle();
		synchronize();
		Profiler.stopGraphics();
	}
	private void synchronize() {
		GraphicsObject gro;
		GSync sync;
		GameObject obj;
		for (int i = 0; i < game.numGameObjects; i++) {
			obj = game.getGameObject(i);
			for (int j = 0; j < obj.numGraphicsObjects; j++) {
				gro = obj.getGraphicsObject(j);
				sync = obj.getSync(j);
				if (sync.type == SyncType.BODY) {
					assert (world.containsBodyId(sync.targetId)) : "Body with the id " + sync.targetId + " not in the world. Did you remove a body that had a GraphicsObject attached to it?";
					gro.syncToBody(world.getBodyById(sync.targetId), sync.offset, sync.angleoffset);
				}
				if (sync.type == SyncType.JOINT) {
					assert (world.containsJointId(sync.targetId)) : "Joint with the id " + sync.targetId + " not in the world. Did you remove a joint that had a GraphicsObject attached to it?";
					gro.syncToJoint(world.getJointById(sync.targetId));
				}
				if (sync.type == SyncType.DYNAMIC) {
					// Don't do anything. Will be done in corresponding
					// GameObject
				}
			}
		}
	}
	public void removeGameObject(GameObject go) {
		while (go.numBodies > 0) {
			go.remBody(go.getBody(0));
		}
		while (go.numJoints > 0) {
			go.remJoint(go.getJoint(0));
		}
		while (go.numGraphicsObjects > 0) {
			go.remGraphicsObject(go.getGraphicsObject(0));
		}
	}
	public void addBody(Body b) {
		world.addBody(b);
	}
	public void remBody(Body b) {
		world.remBody(b);
		removePotentialReferences(SyncType.BODY, b.id);
	}
	private void removePotentialReferences(SyncType type, int id) {
		for (int i = 0; i < game.numGameObjects; i++) {
			GameObject o = game.getGameObject(i);
			for (int j = 0; j < o.numGraphicsObjects; j++) {
				GraphicsObject gro = o.getGraphicsObject(j);
				GSync s = o.getSync(gro);
				if (s.type == type && s.targetId == id) {
					setSync(o, gro, new GSync());
				}
			}
		}
	}
	private void setSync(GameObject o, GraphicsObject gro, GSync gSync) {
		o.setSync(gro, gSync);
	}
	public void addJoint(Joint j) {
		world.addJoint(j);
	}
	public void remJoint(Joint j) {
		world.remJoint(j);
		removePotentialReferences(SyncType.JOINT, j.id);
	}
	public void addGraphicsObject(GraphicsObject o) {
		graphics.addGraphicsObject(o);
	}
	public void remGraphicsObject(GraphicsObject o) {
		graphics.remGraphicsObject(o);
	}
	public boolean isDophysics() {
		return dophysics;
	}
	public void startPhysics() {
		this.dophysics = true;
	}
	public void stopPhysics() {
		this.dophysics = false;
	}
	public void startStopPhysics() {
		this.dophysics = !dophysics;
	}
	public void drawPhysicsDebug() {
		for (int i = 0; i < world.numBodies; i++) {
			Body b = world.getBody(i);
			Shape s = b.shape;
			if (s instanceof Polygon) {
				Polygon p = ((Polygon) s);
				Point v[] = p.points;
				if (!b.sleeps) {
					GLTools.drawLineLoop(v, Color.green);
				}
				else {
					GL11.glEnable(GL11.GL_LINE_STIPPLE);
					GL11.glLineStipple(1, (short) 0xfff0);
					GLTools.drawLineLoop(v, Color.green);
					GL11.glDisable(GL11.GL_LINE_STIPPLE);
				}
				if (drawNormals) {
					for (int j = 0; j < p.points.length - 1; j++) {
						Point c = p.points[j + 1].add(p.points[j]).mul(0.5f);
						Point n = p.lines[j + 1].normalize().normal();
						if (n.mul(b.pos) - n.mul(p.points[j]) > 0) {
							n = n.mul(-1);
						}
						GLTools.drawLine(c, c.add(n.mul(20)), Color.yellow);
					}
					// Point c = p.points[p.points.length -
					// 1].add(p.points[0]).mul(0.5f);
					// Point n = p.lines[0].normalize().normal();
					// if (n.mul(b.pos) - n.mul(p.points[p.points.length - 1]) >
					// 0) {
					// n = n.mul(-1);
					// }
					// GLTools.drawLine(c, c.add(n.mul(20)), Color.yellow);
				}
			}
			else if (s instanceof Circle) {
				GL11.glPushMatrix();
				GL11.glTranslatef(b.pos.x, b.pos.y, 0);
				GL11.glRotatef(Floatmath.radToDeg(b.apos), 0, 0, 1);
				if (!b.sleeps) {
					GLTools.drawCircleLine(new Point(), ((Circle) s).radius, 0, Color.green, false);
				}
				else {
					GL11.glEnable(GL11.GL_LINE_STIPPLE);
					GL11.glLineStipple(1, (short) 0xfff0);
					GLTools.drawCircleLine(new Point(), ((Circle) s).radius, 0, Color.green, false);
					GL11.glDisable(GL11.GL_LINE_STIPPLE);
				}
				GL11.glPopMatrix();
			}
		}
		for (int i = 0; i < world.numJoints; i++) {
			Joint j = world.getJoint(i);
			if (j instanceof TwoBodyJoint && !(j instanceof JAngleJoint)) {
				TwoBodyJoint s = (TwoBodyJoint) j;
				 GLTools.drawLine(s.getP1(), s.getP2(), Color.blue);
				 GLTools.drawPoint(s.getP1(), Color.red, 4);
				 GLTools.drawPoint(s.getP2(), Color.red, 4);
			}
			if (j instanceof JAngleJoint) {
				JAngleJoint ja = (JAngleJoint) j;
				GLTools.drawLine(ja.b2.pos, ja.b2.pos.add(new Point(ja.b1.pos.distance(ja.b2.pos), 0).rotate(-ja.getAngle())), Color.yellow);
				GLTools.drawLine(ja.b1.pos, ja.b2.pos, Color.green);
			}
		}
		GL11.glEnable(GL11.GL_LINE_STIPPLE);
		// GL11.glLineStipple(1, (short) 0xf0f0);
		// for (int i = 0; i < game.numGameObjects; i++) {
		// GameObject o = game.getGameObject(i);
		// for (int j = 0; j < o.numGraphicsObjects; j++) {
		// GraphicsObject gro = o.getGraphicsObject(j);
		// GSync s = o.getSync(gro);
		// if (s.type == SyncType.BODY) {
		// GLTools.drawLine(world.getBodyById(s.targetId).pos, gro.node.pos, Color.black.withAlpha(0.8f));
		// }
		// }
		// }
		GL11.glDisable(GL11.GL_LINE_STIPPLE);
		if (wsys != null) {
			// wsys.draw();
		}
		if (debugpath != null) {
			Vector<Point> path = debugpath.getPoints();
			for (int i = 0; i < path.size() - 1; i++) {
				GLTools.drawLine(path.get(i), path.get(i + 1), Color.blue);
			}
		}
		DebugDraw.draw();
	}
	public boolean drawsNormals() {
		return drawNormals;
	}
	public void setDrawNormals(boolean drawNormals) {
		this.drawNormals = drawNormals;
	}
	public void setDrawWaypointSystem(WaypointSystem wsys) {
		this.wsys = wsys;
	}
}
